/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class TopicFilterCollection : Collection<ITopicFilter>, IBindingList, ISupportInitialize
	{
		#region Public Properties
		public bool HasChanges
		{
			get
			{
				return (initializing || suspended) ? lastChangesValue : hasChanges;
			}
			set
			{
				hasChanges = value;
			}
		}
		#endregion

		#region Private / Protected
		private bool hasChanges, initializing, suspended, lastChangesValue, addingRange, addingRangeAffectedCollection;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicFilterCollection" /> class.
		/// </summary>
		public TopicFilterCollection()
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="TopicFilterCollection" /> class.
		/// </summary>
		public TopicFilterCollection(TopicFilterCollection filters)
		{
			if (filters == null)
				throw new ArgumentNullException("filters");

			foreach (ITopicFilter filter in filters)
				Add(filter.Clone());
		}
		#endregion

		#region Methods
		public void MoveUp(ITopicFilter filter)
		{
			if (filter == null)
				throw new ArgumentNullException("filter");

			int index = IndexOf(filter);
			int newIndex = index - 1;

			RemoveAt(index);
			Insert(newIndex, filter);

			hasChanges = true;
			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, newIndex, index));
		}

		public void MoveDown(ITopicFilter filter)
		{
			if (filter == null)
				throw new ArgumentNullException("filter");

			int index = IndexOf(filter);
			int newIndex = index + 1;

			RemoveAt(index);
			Insert(newIndex, filter);

			hasChanges = true;
			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, newIndex, index));
		}

		public void AddRange(IEnumerable<ITopicFilter> range)
		{
			if (range == null)
				throw new ArgumentNullException("range");

			addingRangeAffectedCollection = false;
			addingRange = true;

			foreach (ITopicFilter filter in range)
				Add(filter);

			addingRange = false;

			if (addingRangeAffectedCollection)
			{
				hasChanges = true;
				OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
			}
		}

		protected override void InsertItem(int index, ITopicFilter item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			if (!Contains(item))
			{
				base.InsertItem(index, item);

				item.PropertyChanged += item_PropertyChanged;

				if (addingRange)
					addingRangeAffectedCollection = true;
				else
				{
					hasChanges = true;
					OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
				}
			}
		}

		protected override void SetItem(int index, ITopicFilter item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			if (!Contains(item))
			{
				this[index].PropertyChanged -= item_PropertyChanged;

				base.SetItem(index, item);

				hasChanges = true;
				item.PropertyChanged += item_PropertyChanged;

				OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
			}
		}

		protected override void ClearItems()
		{
			int count = Count;

			foreach (ITopicFilter item in this)
				item.PropertyChanged -= item_PropertyChanged;

			base.ClearItems();

			if (count > 0)
			{
				hasChanges = true;
				OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
			}
		}

		protected override void RemoveItem(int index)
		{
			this[index].PropertyChanged -= item_PropertyChanged;

			base.RemoveItem(index);

			hasChanges = true;
			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
		}

		public void SuspendChangeTacking()
		{
			if (!suspended)
			{
				suspended = true;
				lastChangesValue = hasChanges;
				hasChanges = false;
			}
		}

		public void ResumeChangeTracking()
		{
			ResumeChangeTracking(true);
		}

		public void ResumeChangeTracking(bool raiseEventIfChanged)
		{
			if (suspended)
			{
				suspended = false;
				bool changed = hasChanges;
				hasChanges = changed || lastChangesValue;

				if (raiseEventIfChanged && changed)
					OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
			}
		}
		#endregion

		#region Events
		private readonly object ItemPropertyChangedEventLock = new object();
		private EventHandler<TopicFilterItemPropertyChangedEventArgs> ItemPropertyChangedEvent;

		/// <summary>
		/// Event raised when a property value has changed on one of the collection's items.
		/// </summary>
		public event EventHandler<TopicFilterItemPropertyChangedEventArgs> ItemPropertyChanged
		{
			add
			{
				lock (ItemPropertyChangedEventLock)
				{
					ItemPropertyChangedEvent += value;
				}
			}
			remove
			{
				lock (ItemPropertyChangedEventLock)
				{
					ItemPropertyChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ItemPropertyChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="PropertyChangedEventArgs" /> object that provides the arguments for the event.</param>
		private void OnItemPropertyChanged(TopicFilterItemPropertyChangedEventArgs e)
		{
			EventHandler<TopicFilterItemPropertyChangedEventArgs> handler = null;

			lock (ItemPropertyChangedEventLock)
			{
				handler = ItemPropertyChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			hasChanges = true;

			ITopicFilter filter = (ITopicFilter) sender;

			OnItemPropertyChanged(new TopicFilterItemPropertyChangedEventArgs(filter, e.PropertyName));
			OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf(filter)));
		}
		#endregion

		#region IBindingList Members
		void IBindingList.AddIndex(PropertyDescriptor property)
		{
			// do nothing
		}

		object IBindingList.AddNew()
		{
			throw new NotSupportedException();
		}

		bool IBindingList.AllowEdit
		{
			get
			{
				return true;
			}
		}

		bool IBindingList.AllowNew
		{
			get
			{
				return false;
			}
		}

		bool IBindingList.AllowRemove
		{
			get
			{
				return true;
			}
		}

		void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
		{
			throw new NotSupportedException();
		}

		int IBindingList.Find(PropertyDescriptor property, object key)
		{
			throw new NotSupportedException();
		}

		bool IBindingList.IsSorted
		{
			get
			{
				return true;
			}
		}

		private readonly object ListChangedEventLock = new object();
		private ListChangedEventHandler ListChangedEvent;

		/// <summary>
		/// Event raised when the collection has changed.
		/// </summary>
		public event ListChangedEventHandler ListChanged
		{
			add
			{
				lock (ListChangedEventLock)
				{
					ListChangedEvent += value;
				}
			}
			remove
			{
				lock (ListChangedEventLock)
				{
					ListChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ListChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="ListChangedEventArgs" /> object that provides the arguments for the event.</param>
		private void OnListChanged(ListChangedEventArgs e)
		{
			if (initializing || suspended)
				return;

			ListChangedEventHandler handler = null;

			lock (ListChangedEventLock)
			{
				handler = ListChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		void IBindingList.RemoveIndex(PropertyDescriptor property)
		{
			// do nothing
		}

		void IBindingList.RemoveSort()
		{
			throw new NotSupportedException();
		}

		ListSortDirection IBindingList.SortDirection
		{
			get
			{
				return ListSortDirection.Ascending;
			}
		}

		PropertyDescriptor IBindingList.SortProperty
		{
			get
			{
				return null;
			}
		}

		bool IBindingList.SupportsChangeNotification
		{
			get
			{
				return true;
			}
		}

		bool IBindingList.SupportsSearching
		{
			get
			{
				return false;
			}
		}

		bool IBindingList.SupportsSorting
		{
			get
			{
				return false;
			}
		}
		#endregion

		#region ISupportInitialize Members
		public void BeginInit()
		{
			if (!initializing)
			{
				initializing = true;
				lastChangesValue = hasChanges;
			}
		}

		public void EndInit()
		{
			if (initializing)
			{
				initializing = false;
				hasChanges = lastChangesValue;
			}
		}
		#endregion
	}
}
